package com.pijunn.report

import grails.converters.JSON;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import com.pijunn.common.MailingAddress;
import com.pijunn.common.enums.ServiceType
import com.pijunn.common.template.TemplateHolder;
import com.pijunn.label.Label;
import com.pijunn.label.LabeledItem;
import com.pijunn.shipping.ShippingRate;
import com.pijunn.shipping.ShippingWeight;
import com.pijunn.users.AppUser;
import com.stripe.Stripe;
import com.stripe.model.Charge;
import com.sun.org.apache.xerces.internal.impl.dv.ValidatedInfo;

class FoundController {

	/**
	 * Dependency injection for the springSecurityService.
	 */
	def springSecurityService
	/**
	 * Dependency injection for the emailerService.
	 */
	def emailerService
	/**
	 * Dependency injection for the linkGenerator.
	 */
	def grailsLinkGenerator
	/**
	 * Dependency injection for the paymentService.
	 */
	def paymentService
	/**
	 * Dependency injection for the configuration parameters.
	 */
	def grailsApplication
	
	DateFormat fmt = new SimpleDateFormat('MM/dd/yyyy HH:mm')
	
    def index() { 
		def cmd = new FoundEventCommand()
		cmd.wizardStep = "identify"
		cmd.user = springSecurityService.currentUser
		render(view: "/found/found", model: [cmd: cmd])
    }
	
	def step(FoundEventCommand cmd) {
		if (cmd.previous)
			return previous(cmd)
			
		if (cmd.hasErrors()) {
			if (log.debugEnabled) log.debug("Form has errors") 
			render view: "/found/found", model: [cmd: cmd]
			return
		}
		
		switch(cmd.wizardStep) {
			case "identify":
				def serialNumber = cmd.serialNumber
				def query = LabeledItem.where {
					label.serialNumber == serialNumber
				}
				LabeledItem item = query.find()
				cmd.itemId = item?.id
				cmd.description = item?.description
				cmd.wizardStep = "confirm"
				break
			case "confirm":
				LabeledItem item = new LabeledItem()
				item.id = cmd.itemId
				FoundEvent event = cmd.eventId ? FoundEvent.get(cmd.eventId) : new FoundEvent()
				event.properties = cmd.properties
				event.foundTime = cmd.foundDate
				event.item = item
				event.processShipping = false
				event.save(flush: true)
				if (event.hasErrors()) {
					cmd.errors = event.errors
					render view: "/found/found", model: [cmd: cmd]
					return
				}
				cmd.eventId = event.id
				cmd.contact = event.finderContact
				cmd.mailing = cmd.contact?.mailingAddress ? cmd.contact?.mailingAddress : new MailingAddress()
				cmd.phone = cmd.contact?.cellPhone ? cmd.contact?.cellPhone : cmd.contact?.phone2
				cmd.useSms = cmd.contact?.cellPhone != null
				cmd.wizardStep = "contact"
				break
			case "contact":
				FoundEvent event = FoundEvent.get(cmd.eventId) 
				Contact contact = cmd.contact?.id ? Contact.get(cmd.contact.id) : new Contact()
				contact.properties = cmd.contact.properties
				contact.mailingAddress = cmd.mailing
				contact.foundEvent = event
				if (cmd.useSms)
				 	contact.cellPhone = cmd.phone 
				else
					contact.phone2 = cmd.phone
				contact.save()
				if (contact.hasErrors()) {
					cmd.errors = contact.errors
					render(view: "/found/found", model: [cmd: cmd])
					return
				}
				// Add contact to the event
				event.finderContact = contact
				event.canContactDirect = cmd.contactdirect
				event.save(flush: true)
				
				cmd.description = event.description
				cmd.location = event.location
				cmd.venue = event.venue
				cmd.contact = contact
				cmd.found = fmt.format(event.foundTime)
				cmd.wizardStep = "review"
				break
			case "review":
				redirect (action: 'foundComplete', params: [eventId: cmd.eventId, uid: springSecurityService.currentUser?.id])
				return
				break
		}
 		 
		render(view: "/found/found", model: [cmd: cmd])
	}
	
	def previous(FoundEventCommand cmd) {
		
		switch(cmd.wizardStep) {
			case "identify":
				break
			case "confirm":
				cmd.wizardStep = "identify"
				break
			case "contact":
				FoundEvent event = FoundEvent.get(cmd.eventId)
				if (event) {
					cmd.location = event.location
					cmd.description = event.description
					cmd.venue = event.venue
					cmd.found = fmt.format(event.foundTime)
				}
				cmd.wizardStep = "confirm"
				break
			case "review":
				FoundEvent event = FoundEvent.get(cmd.eventId)
				if (event) {
					cmd.contactdirect = event.canContactDirect
					cmd.contact = event.finderContact
				}
				if (cmd.contact) {
					cmd.phone = cmd.contact.phone2 ?: cmd.contact.cellPhone
					cmd.mailing = cmd.contact.mailingAddress
				}
				cmd.wizardStep = "contact"
				break
		}

		render(view: "/found/found", model: [cmd: cmd])
	}
	
	// Notify loser and display complete message to finder
	def foundComplete(Integer eventId) {
		FoundEvent event = FoundEvent.get(eventId)
		
		notifyLoser(event)
		notifyFinder(event)
		
		event.notifyFinderTime = new Date()
		event.save()
		
		[eventId: eventId]
	}
	
	private notifyLoser(FoundEvent event) {
		AppUser loser = event.item.label.registeredTo
		
		// E-Mail notification
		def rewardUrl = grailsLinkGenerator.link([action: 'collectReward', params: [eventId: event.id], absolute: true])
		def binding = [event: event, rewardUrl: rewardUrl, user: loser]
		def h = new TemplateHolder([bindingName: 'foundToLoser', to: [loser.contactEMail], subject: "Someone Found your "+event.item.name+"!", binding: binding]);
		log.info("Sending Feedback E-mail to: " + loser.contactEMail)
		emailerService.sendNotificationEmail(h)

		// SMS Notification
		
		// Skype notification
		
	}
	
	private notifyFinder(FoundEvent event) {
		// E-Mail notification
		def binding = [event: event]
		def h = new TemplateHolder([bindingName: 'foundToFinder', to: [event.finderContact.email], subject: "Thanks for your report!", binding: binding]);
		log.info("Sending Feedback E-mail to: " + event.finderContact.email)
		emailerService.sendNotificationEmail(h)
	}
	
	private notifyLoserOfFinder(FoundEvent event) {
		Contact loser = event.LoserContact
		
		// E-Mail notification
		def binding = [event: event, user: loser]
		def h = new TemplateHolder([bindingName: 'foundToLoserOfFinder', to: [loser.contactEMail], subject: "Someone Found your "+event.item.name+"!", binding: binding]);
		log.info("Sending Feedback E-mail to: " + loser.contactEMail)
		emailerService.sendNotificationEmail(h)

		// SMS Notification
		
		// Skype notification
		
	}

	def collectReward() {
		
		CollectRewardCommand cmd = new CollectRewardCommand()
		cmd.eventId = params.int('eventId')
		cmd.event = FoundEvent.get(cmd.eventId)
		cmd.rewardAmount = '$50.00'

		cmd.event.loserRespondTime = new Date()
		cmd.event.save()
		
		[cmd: cmd]
	}
	
	def rewardNext(CollectRewardCommand cmd) {
		cmd.event = FoundEvent.get(cmd.eventId)
		
		if (cmd.previous)
			return rewardPrevious(cmd)
			
		if (cmd.hasErrors()) {
			if (log.debugEnabled) log.debug("Form has errors")
			render view: "/found/collectReward", model: [cmd: cmd]
			return
		}
	
		switch(cmd.wizardStep) {
			case "reward":
				cmd.event.rewardAmount = cmd.reward
				cmd.wizardStep = "shipping"

				AppUser u = springSecurityService.currentUser	
				cmd.contact = locateContact(cmd, u)
				cmd.mailing = cmd.contact.mailingAddress ?: new MailingAddress()
				cmd.phone = cmd.contact.cellPhone ?: cmd.contact.phone2 ?: u.contactCellPhone ?: u.contactOtherPhone
				cmd.useSms = !cmd.contact.cellPhone && !cmd.contact.phone2 ? u.contactCellPhone != "" : cmd.contact.cellPhone != null
				List terms = cmd.event.item.description.split(' ')
				def weight = ShippingWeight.findByNameInList(terms)
				cmd.estimatedWeight = weight?.estimatedWeight ?: 1.0
				break
			case "shipping":
				cmd.event.loserContact = cmd.event.loserContact ?: new Contact()
				cmd.event.loserContact.properties = cmd.contact.properties
				cmd.event.loserContact.mailingAddress = cmd.mailing
				if (cmd.useSms) cmd.event.loserContact.cellPhone = cmd.phone
				else cmd.event.loserContact.phone2 = cmd.phone
				cmd.event.processShipping = cmd.ship
				cmd.event.shippingRate = ShippingRate.findByPackageType(cmd.packageType)
				cmd.shippingAmount = searchShippingRate(cmd.event, cmd.event.shippingRate, cmd.speed.key, cmd.estimatedWeight, cmd.insuredAmount).sum { it.amount }
				cmd.event.save()
				if (cmd.event.hasErrors()) {
					cmd.errors = cmd.event.errors
					render(view: "/found/collectReward", model: [cmd: cmd])
					return
				}
				// Calculate charge amount as reward + shipping + handling
				cmd.event.handlingAmount = paymentService.getHandlingCharge()
				cmd.event.shippingAmount = cmd.shippingAmount
				cmd.event.save()
				cmd.chargeamount = cmd.event.rewardAmount + cmd.event.shippingAmount + cmd.event.handlingAmount
				cmd.wizardStep = "payment"
					break
			case "payment":
				 def amountInCents = (cmd.chargeamount * 100) as Integer
					
				 def chargeParams = [
					'amount': amountInCents,					
					'currency': 'usd',					
					'card': cmd.stripeToken,					
					'description': cmd.event.loserContact.email					
				]
				
				def status
				
				try {	
					Stripe.apiKey = grailsApplication.config.grails.plugins.stripe.secretKey
					Charge.create(chargeParams)					
					status = 'Your purchase was successful.'
				}					
				catch(CardException) {				
					log.error('Payment Error: '+CardException.message)	
					status = 'There was an error processing your credit card.'
					cmd.errors.reject('found.payment.error.'+CardException.code)					
				}
				
				if (cmd.hasErrors()) {
					render(view: "/found/collectReward", model: [cmd: cmd])
					return
				}
				
				//cmd.event.paymentAmount = cmd.chargeamount
				cmd.event.save()
 					
				cmd.wizardStep = "complete"
				break
			case "complete":
				finalizeLost(cmd)
				if (!cmd.event.processShipping) {
					notifyLoserOfFinder(cmd.event)
				}
				break
		}
		
		render(view: "/found/collectReward", model: [cmd: cmd])
	}
	
	def rewardPrevious(CollectRewardCommand cmd) {
		cmd.event = FoundEvent.get(cmd.eventId)
		
		switch(cmd.wizardStep) {
			case "reward":
				break
			case "shipping":
				cmd.rewardAmount = '$' + cmd.event.rewardAmount
				cmd.wizardStep = "reward"
				break
			case "payment":
				cmd.contact = cmd.event.loserContact
				cmd.mailing = cmd.event.loserContact.mailingAddress
				cmd.phone =  cmd.event.loserContact.cellPhone ?: cmd.event.loserContact.phone2
				cmd.useSms = !cmd.event.loserContact.phone2
				cmd.packageType = cmd.event.shippingRate.packageType
				List terms = cmd.event.item.description.split(' ')
				cmd.estimatedWeight = cmd.estimatedWeight ?: ShippingWeight.findByNameInList(terms)?.estimatedWeight
				cmd.wizardStep = "shipping"
				break
			case "complete":
				cmd.wizardStep = "payment"
				break
		}
		
		render(view: "/found/collectReward", model: [cmd: cmd])
	}
	
	/**
	 * Search the shipping rate to come up with the total cost to: 
	 * 1) Ship the empty container, and 2) ship the item to it's owner
	 * 
	 * @param eventId is the Id of the found event
	 * @param packageType the type of the package to ship.  This includes the empty weight
	 * @param speed the shipping method to use.  Faster is more expensive
	 * @param insuredAmount the insured value.  Used for the final leg only
	 * 
	 * @return the total cost
	 */
	def searchShippingRate(FoundEvent event, ShippingRate shippingRate, String speed, Float weight, String insuredAmount) {
		// Build starting address
		def startAddress = new MailingAddress(address1: grailsApplication.config.pijunn.mailing.address.address1,
									city: grailsApplication.config.pijunn.mailing.address.city,
									state: grailsApplication.config.pijunn.mailing.address.state,
									zipCode: grailsApplication.config.pijunn.mailing.address.zipCode,
									country: grailsApplication.config.pijunn.mailing.address.country)
		
		def result = paymentService.getShippingCharge(startAddress, event.finderContact.mailingAddress, shippingRate, ServiceType.valueOf(speed), new BigDecimal(insuredAmount ?: "0"))
		
		def rate = [ result.ratedShipmentDetails[0].shipmentRateDetail.totalNetCharge.max { it.amount } ]
		
		shippingRate.emptyWeight = weight
		result = paymentService.getShippingCharge(event.finderContact.mailingAddress, event.loserContact?.mailingAddress ?: springSecurityService.currentUser.mailingAddress, shippingRate, ServiceType.valueOf(speed), new BigDecimal(insuredAmount ?: "0"))
		rate << result.ratedShipmentDetails[0].shipmentRateDetail.totalNetCharge.max { it.amount }
		
		rate
	}
	
	def searchShippingRateJson(int eventId, String packageType, String speed, Float weight, String insuredAmount) {
		FoundEvent event = FoundEvent.get(eventId)
		def shippingRate = ShippingRate.findByPackageType(packageType)
		
		def rate = searchShippingRate(event, shippingRate, speed, weight, insuredAmount)
	
		render rate as JSON
	}
	
	private Contact locateContact(CollectRewardCommand cmd, AppUser u) {
		// Is there a Lost report for this item
		def lost = LostEvent.withCriteria {
			isNull("foundTime")
			item {
				eq("id", cmd.event.item.id)
			}
		}
		def contact = lost.contact
	
		cmd.event.loserContact ?: contact[0] ?: new Contact(name: u.firstName + ' ' + u.lastName, mailingAddress: u.mailingAddress, email: u.contactEMail, skypeId: u.contactSkypeId)
	}
	
	private void finalizeLost(CollectRewardCommand cmd) {
		def lost = LostEvent.withCriteria {
			isNull("foundTime")
			item {
				eq("id", cmd.event.item.id)
			}
		}

		if (lost.size() > 0) {
			lost.each { 
				it.foundTime = new Date()
				it.save()
			}
		}
	}
}
